---
title: Storage
description: Upload & download files to Cloudflare R2.
---

import { Aside } from "@astrojs/starlight/components";

[Cloudflare R2](https://developers.cloudflare.com/r2/) is an object storage solution that's S3 compatible, global, scalable, and can be used to store files, images, videos, and more! It integrates natively with Cloudflare workers, and therefore, with Redwood. It is available locally during development, and is automatically configured when you deploy to Cloudflare.

<Aside type="tip" title="Pick your poison">
  You do not need to use R2, you can use any storage solution you want, but we
  recommend R2!
</Aside>

## Setup

To use R2 in your project, you need to create a R2 bucket, and bind it to your worker.

```bash showLineNumbers=false withOutput
> npx wrangler r2 bucket create my-bucket

Creating bucket 'my-bucket'...
✅ Created bucket 'my-bucket' with default storage class of Standard.

Configure your Worker to write objects to this bucket:

{
  "r2_buckets": [
    {
      "bucket_name": "my-bucket",
      "binding": "R2",
    },
  ],
}
```

This will create a bucket called `my-bucket`, which you'll have to bind to your worker, which you do by pasting the above into your `wrangler.jsonc` file.

```jsonc title="wrangler.jsonc"
{
  "r2_buckets": [
    {
      "bucket_name": "my-bucket",
      "binding": "R2",
    },
  ],
}
```

After updating `wrangler.jsonc`, run `pnpm generate` to update the generated type definitions.

This will make the `my-bucket` bucket available via the `env.R2` binding in your worker. You can then use this binding to upload, download, and manage files stored in R2 using the standard R2 API.

### Naming

Bucket names must begin and end with an alphanumeric and can only contain letters (a-z), numbers (0-9), and hyphens (-).

## Usage

RedwoodSDK uses the standard Request/Response objects. When uploading files, the data is streamed directly from the client to R2 storage. Similarly, when downloading files, they are streamed directly from R2 to the client. This streaming approach means files are processed in small chunks rather than loading the entire file into memory, making it memory-efficient and suitable for handling large files.

### Uploading Files

```tsx title="src/worker.tsx"
import { defineApp } from "rwsdk/worker";
import { route } from "rwsdk/router";
import { env } from "cloudflare:workers";

defineApp([
  route("/upload/", async ({ request }) => {
    const formData = await request.formData();
    const file = formData.get("file") as File;

    // Stream the file directly to R2
    const r2ObjectKey = `/storage/${file.name}`;
    await env.R2.put(r2ObjectKey, file.stream(), {
      httpMetadata: {
        contentType: file.type,
      },
    });

    return new Response(JSON.stringify({ key: r2ObjectKey }), {
      status: 200,
      headers: {
        "Content-Type": "application/json",
      },
    });
  }),
]);
```

### Downloading Files

```tsx title="src/worker.tsx"
import { defineApp } from "rwsdk/worker";
import { route } from "rwsdk/router";
import { env } from "cloudflare:workers";

defineApp([
  route("/download/*", async ({ request, params }) => {
    const object = await env.R2.get("/storage/" + params.$0);
    if (object === null) {
      return new Response("Object Not Found", { status: 404 });
    }
    return new Response(object.body, {
      headers: {
        "Content-Type": object.httpMetadata?.contentType as string,
      },
    });
  }),
]);
```
